<?php

namespace app\service;

use MongoDB\BSON\Regex;
use MongoDB\Driver\BulkWrite;
use MongoDB\Driver\Command;
use MongoDB\Driver\Manager;
use MongoDB\Driver\Query;
use MongoDB\Driver\WriteConcern;

/**
 * Class MongoHelper
 * Mongodb curd 增删改查、聚合查询等操作
 */
class MongoHelper
{
    //排序
    const SORT_ASC = 1; //正序
    const SORT_DESC = -1; //倒序

    /**
     * mongodb的基础连接配置
     * @var array
     */
    private static $_mdb = [
        // 默认连接库
        'default' => ['ip' => '127.0.0.1', 'port' => 27017, 'db' => '', 'collection' => ''],
        'mdb1' => ['ip' => '127.0.0.1', 'port' => 27000, 'db' => '', 'collection' => ''],
    ];

    // 查询表达式
    protected $exp = [
        '!=' => 'ne', '<>' => 'ne', 'neq' => 'ne', '=' => 'eq', '>' => 'gt', '>=' => 'gte', '<' => 'lt', '<=' => 'lte',
        'in' => 'in', 'not in' => 'not in', 'nin' => 'not in', 'mod' => 'mod', 'exists' => 'exists',
        'null' => 'null', 'notnull' => 'not null', 'not null' => 'not null', 'regex' => 'regex', 'type' => 'type', 'all' => 'all',
        '> time' => '> time', '< time' => '< time', 'between' => 'between', 'not between' => 'not between', 'between time' => 'between time',
        'not between time' => 'not between time', 'notbetween time' => 'not between time',
        'like' => 'like', 'near' => 'near', 'size' => 'size'
    ];

    private $instanceManager; //MongoDB\Driver\Manager 对象
    private $instanceBulkWrite; //MongoDB\Driver\BulkWrite 对象， 相当于一个加载器
    private static $instance = [];
    private static $def = 'default';
    private static $confKey;
    private $config = [];

    // 查询解析器
    private $fieldOptions; //字段表达式
    private $sortOptions; //sort表达式
    private $limitOptions; //limit表达式
    private $skipOptions; //skip表达式
    private $whereOptions; //where条件表达式
    private $groupOptions; //分组表达式

    /**
     * MongoCurd constructor.
     */
    private function __construct()
    {
        $this->config = self::$_mdb[self::$confKey];
        $this->instanceManager = new Manager("mongodb://{$this->config['ip']}:{$this->config['port']}");
        $this->instanceBulkWrite = new BulkWrite();
    }

    /**
     * @return $this
     */
    public static function getInstance($confKey = NULL)
    {
        if (!$confKey || !isset(self::$_mdb[$confKey])) {
            $confKey = self::$def;
        }
        self::$confKey = $confKey;

        if (!isset(self::$instance[$confKey])) {
            self::$instance[$confKey] = new self();
        }

        return self::$instance[$confKey];
    }

    /**
     * 申明集合的：数据库 / 集合
     * @param string $collection
     * @param string $db
     * @return $this
     */
    public function namespaces($db = '', $collection = '')
    {
        if (strpos($db, '.') !== false) { //支持格式：'db.col'
            list($db, $collection) = explode('.', $db);
        }
        $this->config['db'] = $db;
        $this->config['collection'] = $collection;
        return $this;
    }

    /**
     * @param array $docs
     * @param string $replaceField 替换插入的字段，即允许replace写入，当有值的时候表示替换写入，为空时即普通插入
     * @return int|null
     */
    public function insert(array $docs, $replaceField = '')
    {
        $counts = null;
        try { //如果指定了 _id，则可能会出现重复的主键冲突异常
            //① 初始化一个bulk对象
            $this->_insertRecursion($docs, $replaceField);
            //② 加载写入的文档完成，执行连接数据库，运行写入操作
            $writeConcern = new WriteConcern(WriteConcern::MAJORITY, 1000); //毫秒
            //③ 返回结果
            $instanceWriteResult = $this->instanceManager->executeBulkWrite("{$this->config['db']}.{$this->config['collection']}", $this->instanceBulkWrite, $writeConcern);
            $counts = $instanceWriteResult->getInsertedCount(); //获取写入的条数
            //④ 执行完BulkWrite后，重置this->bulkWrite对象，一个BulkWrite只能执行一次
            $this->instanceBulkWrite = new BulkWrite();
        } catch (Exception $e) {
            $this->_exceptionNotify($e); //打印异常
        }
        return $counts;
    }

    /**
     * insert - 递归填装 BulkWrite
     * @param array $docs 支持多维数组，如：[ k1 => v1, k2 => v2...  ]  / [ [kk1 => vv1, kk2 => vv2...], [kk1 => vv1, kk2 => vv2...] ]
     * @param string $replaceField 替换插入的字段，即允许replace写入，当有值的时候表示替换写入，为空时即普通插入
     * @throws Exception
     */
    private function _insertRecursion(array $docs, $replaceField = '')
    {
        //当字段唯一替换字段不是字符串时，抛出异常
        if (!is_string($replaceField)) {
            throw new Exception('非法的请求类型replaceField：' . gettype($replaceField), -1006);
        }
        foreach ($docs as $_keyOrIndex => $_doc) {
            if (is_numeric($_keyOrIndex) && is_array($_doc)) {
                $this->_insertRecursion($_doc, $replaceField);
            } else { //判断为一维数组，即可以直接写入的数据
                if (!$replaceField) {
                    $this->instanceBulkWrite->insert($docs); //返回写入后生成的ObjectId
                } else {
                    // multi（多种）：当multi=false时，表示只更新一条记录； |  如果upsert=true时，则multi不能被设置为true，必须是false
                    $this->instanceBulkWrite->update([$replaceField => $docs[$replaceField]], ['$set' => $docs], ['multi' => false, 'upsert' => true]);
                }
                break;
            }
        }
    }

    /**
     * 查询一条
     */
    public function findOne()
    {
        return $this->_query(true);
    }

    /**
     * 示例：
     *  ★ 如果字段是一个数组，如：
     *      > db.col.insert({fruit: ["apple", "banana", "orange"]});
     *      > db.col.find({fruit: "apple"});  //这种查询方式会匹配上述文档，相当于查询：{fruit: "apple", fruit: "banana", fruit: "orange"}
     *
     * 查多条
     * @return array
     */
    public function find()
    {
        return $this->_query();
    }

    /**
     * 查询执行体
     * @param false $onlyOne 是否值查一条
     * @return array
     */
    private function _query($onlyOne = false)
    {
        $filters = $this->whereOptions;
        $options = [
            'projection' => $this->fieldOptions ?: [], //查询哪些字段
            'sort' => $this->sortOptions ?: [],  //排序规则
            'skip' => $this->skipOptions,  //跳过
            'limit' => $onlyOne ? 1 : $this->limitOptions, //查几条
        ];

        $query = new Query($filters, $options);
        // 执行查询
        $result = [];
        try {
            // 返回一个cursor对象
            $Cursor = $this->instanceManager->executeQuery("{$this->config['db']}.{$this->config['collection']}", $query, $options);
            // 格式化结果集
            foreach ($Cursor as $_obj) {
                $result[] = $this->_obj2Array($_obj);
            }
            // options条件重置
            $this->optionsReset();
        } catch (\MongoDB\Driver\Exception\Exception $e) {
            $this->_exceptionNotify($e);
        }
        return $result ? ($onlyOne ? $result[0] : $result) : [];
    }

    public function findArray()
    {
        //TODO...
    }

    /**
     * 解析可查询的字段，支持2种模式：
     *      - string : 如，"name" / "name,age..." / "*"
     *      - array  : 如，['name', 'age' ...]
     *
     * @param string|array $fieldOptions "name,age..." | ['name', 'age' ...]
     * @return $this
     */
    public function field($fieldOptions = null)
    {
        $express = []; //解析后的表达式
        if (is_null($fieldOptions) || $fieldOptions == '*') {
            $this->fieldOptions = [];
            return $this;  //不传 or 传* 都不做解析
        }
        //① 检测参数类型，则抛出异常
        try {
            if (!is_string($fieldOptions) && !is_array($fieldOptions)) {
                throw new Exception("不支持的参数类型：" . gettype($fieldOptions), -1000);
            }
        } catch (Exception $e) {
            $this->_exceptionNotify($e); //打印异常
        }
        //② 字符串模式
        if (is_string($fieldOptions)) {
            $fieldOptions = explode(',', $fieldOptions);
        }
        //③ 数组模式
        foreach ($fieldOptions as $_field) {
            $express[$_field] = 1;
        }
        //④ 解析后的结果保存
        if (!array_key_exists('_id', $express)) {  //_id 字段，除非特别标出，否则每次都会被结果返回
            $express['_id'] = 0;
        }
        //⑤ 保存`field`表达式
        $this->fieldOptions = $express;

        return $this;
    }

    /**
     * @param string|array $fieldOrArray 指定字段 or 配置数组： [ 'field1' => 1, 'field2' => -1... ]
     * @param int $SORT 排序顺序，1：正序 ， -1：倒序
     * @param mixed ...$options 其他字段
     * @return $this
     * @example
     *      ● order([ f1 => MongoCurd::SORT_ASC, f2 => MongoCurd::SORT_DESC... ])
     *      ● order( f1, MongoCurd::SORT_ASC, f2, MongoCurd::SORT_ASC... )
     *
     */
    public function order($fieldOrArray, $SORT = self::SORT_ASC, ...$options)
    {
        if (is_array($fieldOrArray)) {
            $this->sortOptions = $fieldOrArray;  //保存`sort`表达式
        } else {
            $args = func_get_args(); //获取参数
            $argCounts = count($args); //参数的数量
            //检查参数是否配对（为偶数）
            try {
                if ($argCounts % 2 != 0) {
                    throw new Exception('参数貌似不配对，请检查参数个数！', -1001);
                }
            } catch (Exception $e) {
                $this->_exceptionNotify($e); //打印异常
            }

            //格式化排序数组
            $sortFormat = [];
            for ($i = 0; $i <= $argCounts; $i += 2) {
                if ($i >= $argCounts) break; //控制循环不越界
                $sortFormat[$args[$i]] = $args[$i + 1];
            }
            $this->sortOptions = $sortFormat;  //保存`sort`表达式
        }

        return $this;
    }

    /**
     * 取多少条数据、忽略多少条数据
     * @param int $limit
     * @param int $skip
     * @return $this
     */
    public function limit($limit, $skip = 0)
    {
        $this->limitOptions = $limit;
        $this->skipOptions = $skip;

        return $this;
    }

    /**
     * @param array|string $fields 分组字段。如：name / name,age / [name, age]
     */
    public function group($fields)
    {
        $this->groupOptions = $fields;
    }

    /**
     * 条件查询，调用示例：
     *      ▶ 只传一个数组：可能是一维数组（ [ field1 => v1, field2 => v2... ]） | 可能是多维数组（ [ [field1, 'exp', v1], [field2, 'exp', v2]... ] ）
     *          ● where([name => 张三, age => 18...]) | 单数组上传参数，一维数组只解析为：=，如果value配置非基本类型，如：数组、对象等，则抛出异常
     *          ● where([ [name, = , 张三]， [age, =, 18]...) | 单数组上传参数，一维数组只解析为：=，如果value配置非基本类型，如：数组、对象等，则抛出异常
     *      ▶ 常规传递，field是指定字段字符串
     *          ● where(name, 张三)
     *          ● where(name, !=，张三)
     *
     * @param string|array $field 可能是指定字段，也可能是条件配置数组
     * @param mixed $op 对比运算符 | 值，当传只传了2个参数的时候，那就是相当于“=”操作
     * @param null $condition 对比条件值
     * @return $this
     */
    public function where($field, $op = null, $condition = null)
    {
        $args = func_get_args();
        array_shift($args); //删除参数的第一个参数，即：删除`field`字段
        try {
            $this->parseWhereExp('$and', $field, $op, $condition);
        } catch (Exception $e) {
            $this->_exceptionNotify($e);
        }

        return $this;
    }

    /**
     * Or条件查询，当前传递的
     * 条件查询，调用示例：
     *      ▶ 只传一个数组：可能是一维数组（ [ field1 => v1, field2 => v2... ]） | 可能是多维数组（ [ [field1, 'exp', v1], [field2, 'exp', v2]... ] ）
     *          ● whereOr([name => 张三, age => 18...]) | 单数组上传参数，一维数组只解析为：=，如果value配置非基本类型，如：数组、对象等，则抛出异常
     *          ● whereOr([ [name, = , 张三]， [age, =, 18]...]) | 单数组上传参数，一维数组只解析为：=，如果value配置非基本类型，如：数组、对象等，则抛出异常
     *      ▶ 常规传递，field是指定字段字符串
     *          ● whereOr(name, 张三)
     *          ● whereOr(name, !=，张三)
     *
     * @param $field
     * @param null $op
     * @param null $condition
     * @return $this
     */
    public function whereOr($field, $op = null, $condition = null)
    {
        $args = func_get_args();
        array_shift($args); //删除参数的第一个参数，即：删除`field`字段
        try {
            $this->parseWhereExp('$or', $field, $op, $condition);
        } catch (Exception $e) {
            $this->_exceptionNotify($e);
        }

        return $this;
    }

    /**
     * @param $field
     * @param $condition
     * @param string $logic
     * @return $this
     */
    public function whereIn($field, $condition, $logic = 'AND')
    {
        try {
            $logic = strtolower($logic);
            $this->parseWhereExp('$' . $logic, $field, 'IN', $condition);
        } catch (Exception $e) {
            $this->_exceptionNotify($e);
        }

        return $this;
    }

    /**
     * @param $field
     * @param $condition
     * @param string $logic
     * @return $this
     */
    public function whereNotIn($field, $condition, $logic = 'AND')
    {
        try {
            $logic = strtolower($logic);
            $this->parseWhereExp('$' . $logic, $field, 'NOT IN', $condition);
        } catch (Exception $e) {
            $this->_exceptionNotify($e);
        }

        return $this;
    }

    /**
     * @param $field
     * @param $condition
     * @param string $logic
     * @return $this
     */
    public function whereLike($field, $condition, $logic = 'AND')
    {
        try {
            $logic = strtolower($logic);
            $this->parseWhereExp('$' . $logic, $field, 'LIKE', $condition);
        } catch (Exception $e) {
            $this->_exceptionNotify($e);
        }

        return $this;
    }

    /**
     * @param $field
     * @param $condition
     * @param string $logic
     * @return $this
     */
    public function whereNotLike($field, $condition, $logic = 'AND')
    {
        try {
            $logic = strtolower($logic);
            $this->parseWhereExp('$' . $logic, $field, 'NOT LIKE', $condition);
        } catch (Exception $e) {
            $this->_exceptionNotify($e);
        }

        return $this;
    }

    /**
     * 解析where条件表达式
     * @param $logic
     * @param $field
     * @param $op
     * @param $condition
     * @throws Exception
     */
    protected function parseWhereExp($logic, $field, $op, $condition)
    {
        $where = [];
        //① 数组形式传参
        if (is_array($field)) { //示例：[ field1 => v1, field2 => v2... ]，此时只支持一维数组，如果v的值传数组或其他非基本类型，则抛异常
            $where = $this->_parseArrayWhereItems($field); //解析数组形式的where参数
        } //② 常规解析，传了field是字符串
        elseif (is_string($field)) {
            if (is_null($condition)) { //只传了2个参数
                $condition = $op;
                $op = '=';
            }
            $where = $this->_parseWhereItem($field, $op, $condition);
        }
        //③ 如果解析有值，判断options的情况合并或设置
        if ($where) {
            $this->whereOptions[$logic] = isset($this->whereOptions[$logic]) ? array_merge($this->whereOptions[$logic], $where) : $where;
        }
    }

    /**
     * 链式条件重置
     */
    protected function optionsReset()
    {
        $this->whereOptions = null; //重置where条件
        $this->limitOptions = null; //重置limit条件
        $this->sortOptions = null;
        $this->skipOptions = null;
        $this->fieldOptions = null;
    }

    /**
     * 解析数组形式的where条件
     * @param array $field
     * @return array
     * @throws Exception
     */
    private function _parseArrayWhereItems(array $field)
    {
        $where = [];
        //① 一维数组解析，示例：[ f1 => v1, f2 => v2... ]
        if (key($field) !== 0) {
            foreach ($field as $_key => $_val) {
                if (is_null($_val)) { //解析null，判断一个字段的值为null，并且字段是存在的
                    $where[] = [$_key => ['$in' => [null], '$exists' => true]];
                } elseif (is_array($_val)) { //解析val为数组
                    $where[] = [$_key => ['$in' => $_val]];
                } else {//基本类型val解析，解析为：等于
                    $where[] = [$_key => $_val];
                }
            }
        } //② 多维数组解析，如：[ [name, = , 张三], [age, >, 18 ] ...]
        else {
            foreach ($field as $_item) {
                list($_field, $_exp, $_val) = $_item;
                $_w = $this->_parseWhereItem($_field, $_exp, $_val); //依次解析
                $where = array_merge($where, $_w);
            }
        }
        return $where;
    }

    /**
     * 解析where条件
     * @param string $field 表字段
     * @param string $exp 对比表达式，如：= / >= / in ...
     * @param string|array $condition 条件
     * @return array|array[]|array[][]|string[][]
     * @throws Exception
     */
    private function _parseWhereItem($field, $exp, $condition)
    {
        $exp = strtolower($exp); //比较符转为小写
        //判断比较条件符是否支持
        if (!in_array($exp, $this->exp) && !array_key_exists($exp, $this->exp)) {
            throw new Exception("不支持的条件运算符：{$exp}", -1003);
        }
        //获取到正确的条件运算符
        if (array_key_exists($exp, $this->exp)) {
            $exp = $this->exp[$exp]; //调减运算符
        }
        //根据不同的条件运算符进行解析
        switch ($exp) {
            case 'eq': //不等于，如：[ age, =, 18 ]
                $result[] = [$field => $condition];
                break;
            case 'ne': //不等于，如：[ age, ne, 18 ]
            case 'gt': //大于，如：[ age, >, 18 ]
            case 'gte': //大于等于，如：[ age , >=, 18 ]
            case 'lt': //小于，如：[ age, <, 18 ]
            case 'lte': //小于等于，如：[ age , <=, 18 ]
                $result[] = [$field => ['$' . $exp => $condition]];
                break;
            case 'between': //在范围内，如：[age, between, [18,30] ]
                if (is_string($condition)) {
                    $condition = explode(',', $condition); //如：[ age, between, '18,30' ]
                }
                list($_minVal, $_maxVal) = $condition;
                $result[] = [$field => ['$gte' => $_minVal, '$lte' => $_maxVal]];
                break;
            case 'not between': //在范围内，如：[ 'not between' => [18,30] ]
                if (is_string($condition)) {
                    $condition = explode(',', $condition); //如：[ age, between, '18,30' ]
                }
                list($_minVal, $_maxVal) = $condition;
                $result[] = [$field => ['$not' => ['$gte' => $_minVal, '$lte' => $_maxVal]]];
                break;
            case 'in': //示例： [ 'name' => [ 'in', ['张三', '李四'...] ]]
                if (is_string($condition)) {
                    $condition = explode(',', $condition); //如：[ age, in, '18,30' ]
                }
                $result[] = [$field => ['$in' => $condition]];
                break;
            case 'not in': //示例： [ 'name' => [ 'not in', ['张三', '李四'...] ]]
                if (is_string($condition)) {
                    $condition = explode(',', $condition); //如：[ age, in, '18,30' ]
                }
                $result[] = [$field => ['$not' => ['$in' => $condition]]];
                break;
            case 'like': //示例：[ 'name', 'like', '%lily%' ]
                $firstWord = substr($condition, 0, 1); //条件的第一个字符
                $lastWord = substr($condition, -1); //条件的最后一个字符
                $condition = trim($condition, '%'); //去除掉首尾 ‘%’ 符号
                $head = $tail = ''; //正则表达的开头 、 结尾
                if ($firstWord != '%') {
                    $head = '^';
                }
                if ($lastWord != '%') {
                    $tail = '$';
                }
                // 调用正则表达式的时候，需要使用 MongoDB\BSON\Regex::__construct() 初始化，且不需要添加 '/'
                $result[] = [$field => ['$regex' => new Regex("{$head}{$condition}{$tail}")]];
                break;
            case 'regex': //正则表达式，示例：[ 'name' , 'regex', '/^张*/i' ]
                $result[] = [$field => ['$regex' => new Regex($condition)]];
                break;

            default:
                throw new Exception("暂不支持的表达式：{$exp}", -1004);
        }

        return $result;
    }

    /**
     * 示例：
     *      ★ $set: 修改器，用来指定一个键的值，如果不存在则创建它（能在文档大小不变的情况下立即修改，否则性能会有所下降，因为重新分配空间会导致性能下降）
     *          - db.col.update({filter: xxx}, {$set: {update1: xx1, update2:xx2..}, [options]});
     *
     *      ★ $unset: 指定某个键并删除
     *          - db.col.update({filter: xxx}, {$unset : {field1: 1}});
     *
     *      ★ $inc: 自增（速度快，因为不改表文档的大小）
     *          - db.col.update({filter: xxx}, {$inc: {$field: 1}， $set: {update1: xx1...}});
     *
     *      ★ $push: 数组字段操作，向已有的数组字段末尾push一个元素，如果字段不存在则创建一个新数组（$push会导致文档大小发生变化导致空间重新分配，可能会成为瓶颈；可以将内嵌数组独立出来，放到一个单独的集合里）
     *          - db.col.update({filter: xxx},  {$push: {field1: { name: '张三', age: 18, gander: '男' }, field2:{...}... }});
     *
     *      ★ $addToSet: 向数组字段（Set类型，即元素不可重复）中添加元素，当元素不存在则添加；若存在则不添加
     *          - db.col.update({filter: xxx},  {$addToSet: {fieldArr: {"key" :"item"...}}}, {multi: true});
     *          - db.col.update({filter: xxx},  {$addToSet: {fieldArr: ["item1", "item2"...]}, {multi: true}}); //这种方式，只会把数组元素当作一个整体处理塞进到字段尾部，不会将item1、item2...分开添加
     *
     *      ★ $addToSet + $each : 向数组字段（Set类型，元素唯一）中一次操作添加多条元素
     *          - db.col.update({filter: xxx}, {$addToSet: {filedArr: {$each: ["item1", "item2"...]}}}, {multi: true});
     *
     *      ★ $pop: 数组字段，删除数组字段中的一个元素（1：从尾部删除， -1：从头部删除）,如：fieldArr=[item1, item2, item3]
     *          - db.col.update({filter: xxx}, {$pop: {fieldArr: -1}}, {multi: true}); //删除后，fieldArr=[item2, item3]
     *          - db.col.update({filter: xxx}, {$pop: {filedArr: 1}}, {multi: true}); //删除后，fieldArr=[item1, item2]
     *
     *      ★ $pull: 数组字段，删除匹配的全部元素，如：fieldArr=[t1, t2, t3, t1, t1]
     *          - db.col.update({filter: xxx}, {$pull: {fieldArr: "t1"}}, {multi: true}); //删除后，fieldArr=[t2, t3]
     *
     *      ★
     * 更新
     * @param array $data
     * @return int|null
     */
    public function update(array $data)
    {
        //获取where条件
        $filter = $this->whereOptions ?: [];
        //载入bulkWrite执行器
        $this->instanceBulkWrite->update($filter, ['$set' => $data], ['multi' => true]); //开启 multi=true，允许更新多条记录
        //执行
        $writeConcern = new WriteConcern(WriteConcern::MAJORITY, 1000); //毫秒
        $namespace = "{$this->config['db']}.{$this->config['collection']}"; //db.collection
        $WriteResult = $this->instanceManager->executeBulkWrite($namespace, $this->instanceBulkWrite, $writeConcern); //返回MongoDB\Driver\WriteResult
        //执行完BulkWrite后，重置this->bulkWrite对象，一个BulkWrite只能执行一次
        $this->instanceBulkWrite = new BulkWrite();
        //options条件重置
        $this->optionsReset();
        return $WriteResult->getModifiedCount(); //获取返回的被更新的记录条数
    }

    /**
     * 自增
     * @param $field
     * @param int $increment
     * @return int|null
     */
    public function inc($field, $increment = 1)
    {
        //获取where条件
        $filter = $this->whereOptions ?: [];
        //载入BulkWrite执行器
        $this->instanceBulkWrite->update($filter, ['$inc' => ['$' . $field => $increment]], ['upsert' => false, 'multi' => true]);
        //执行
        $WriteConcern = new WriteConcern(WriteConcern::MAJORITY, 100); //毫秒
        $namespace = "{$this->config['db']}{$this->config['collection']}";
        $WriteResult = $this->instanceManager->executeBulkWrite($namespace, $this->instanceBulkWrite, $WriteConcern); //返回MongoDB\Driver\WriteResult
        //执行完后重置BulkWrite对象，因为只能执行一次
        $this->instanceBulkWrite = new BulkWrite();
        //充值全部的条件
        $this->optionsReset();

        return $WriteResult->getModifiedCount();
    }

    /**
     * 自减
     * @param $field
     * @param int $decrement
     * @return int|null
     */
    public function dec($field, $decrement = 1)
    {
        return $this->inc($field, -$decrement);
    }

    /**
     * 删除
     * @return int|null
     */
    public function delete()
    {
        //获取删除条件
        $filter = $this->whereOptions ?: [];
        //载入对象执行器
        $this->instanceBulkWrite->delete($filter, ['limit' => false]); //limit默认为false，即删除所有匹配的记录；当limit=true时，只删除第一条匹配的记录
        //执行删除
        $WriteConcern = new WriteConcern(WriteConcern::MAJORITY, 1000); //毫秒
        $namespace = "{$this->config['db']}.{$this->config['collection']}"; //db.collection
        $WriteResult = $this->instanceManager->executeBulkWrite($namespace, $this->instanceBulkWrite, $WriteConcern);
        //执行完BulkWrite后，重置this->bulkWrite对象，一个BulkWrite只能执行一次
        $this->instanceBulkWrite = new BulkWrite();
        //options条件重置
        $this->optionsReset();
        return $WriteResult->getDeletedCount(); //返回被删除的记录条数
    }

    /**
     * 最大值
     * @param $field
     * @return int|mixed|null
     */
    public function max($field)
    {
        return $this->aggregate('max', $field, false);
    }

    /**
     * 最小值
     * @param $field
     * @return int|mixed|null
     */
    public function min($field)
    {
        return $this->aggregate('min', $field, false);
    }

    /**
     * 平均值
     * @param $field
     * @return int|mixed|null
     */
    public function avg($field)
    {
        return $this->aggregate('avg', $field, true);
    }

    /**
     * 总值
     * @param $field
     * @return int|mixed|null
     */
    public function sum($field)
    {
        return $this->aggregate('sum', $field, true);
    }

    /**
     * 统计
     * @return int|mixed|null
     */
    public function count()
    {
        $this->groupOptions = null;
        return $this->aggregate('sum', 1, true);
    }

    /**
     * 示例：
     *      - db.runCommand({
     *          "findAndModify": "col_name",
     *          "query": {status: "100"},
     *          "sort": {age: -1},
     *          "update": {$set: {class: "PHP"}},
     *        })
     *
     * 直接执行命令行，调用者自行构建命令
     * @param $cmd
     * @return array
     */
    public function cmd($cmd)
    {
        $cmd = new Command($cmd); //第二个参数，忽略
        //执行命令
        $result = [];
        try {
            $Cursor = $this->instanceManager->executeCommand($this->config['db'], $cmd);
            $cursorArr = $Cursor->toArray(); //游标结果转为数组
            foreach ($cursorArr as $_obj) {
                $result[] = $this->_obj2Array($_obj);
            }
        } catch (\MongoDB\Driver\Exception\Exception $e) {
            $this->_exceptionNotify($e);
        }
        return $result;
    }

    /**
     * @param string $aggregate 聚合指定，如： sum / avg / min / max / count...
     * @param string $field 聚合的字段
     * @param bool $forceInt 是否强转为int
     * @return int|mixed|null
     */
    protected function aggregate($aggregate, $field, $forceInt)
    {
        //初始化命令
        $cmd = [
            'aggregate' => $this->config['collection'],  //指明聚合的集合名称
            'allowDiskUse' => true, //group有100M内存的限制，如果超过这个限制会直接返回error；设置allowDiskUse为true来避免异常，allowDiskUse为true将利用临时文件来辅助实现group操作
            'cursor' => new \stdClass(),
            'pipeline' => [ //管道
                ['$match' => $this->whereOptions ?: new \stdClass()], //解析where条件， $match必须对应一个对象
                ['$group' => ['_id' => $this->groupOptions ?: null, 'aggregate' => ['$' . $aggregate => is_int($field) ? $field : '$' . $field]]], //解析group
            ],
        ];

        //载入命令配置
        $cmd = new Command($cmd); //第二个参数，忽略
        //执行命令
        $result = null;
        try {
            $Cursor = $this->instanceManager->executeCommand($this->config['db'], $cmd);
            $cursorArr = $Cursor->toArray(); //游标结果转为数组
            $result = $cursorArr ? $this->_obj2Array($cursorArr[0])['aggregate'] : $result;
            if ($forceInt) { //强转为 int
                $result = intval($result);
            }
        } catch (\MongoDB\Driver\Exception\Exception $e) {
            $this->_exceptionNotify($e);
        }
        return $result;
    }

    /**
     * 终止并打印异常信息
     * @param Exception $e
     */
    private function _exceptionNotify(Exception $e)
    {
        header("content-type=application\json;charset=utf-8;");
        $notify = [
            'code' => $e->getCode(),
            'msg' => $e->getMessage(),
            'trace' => $e->getTraceAsString(),
            'file' => $e->getFile(),
            'line' => $e->getLine(),
        ];
        echo json_encode($notify, 256);
        exit;
    }

    /**
     * 对象转换成数组
     * @param \stdClass $class
     * @return array
     */
    private function _obj2Array(\stdClass $class)
    {
        return json_decode(json_encode($class), true) ?: [];
    }
}